package etc

import (
	"UserServer/api/internal/config"
	"context"
	"fmt"
	clientv3 "go.etcd.io/etcd/client/v3"
	"log"
	"time"
)

// 该服务发现是一个简单的服务发现，在发现服务之后就会关闭，结束协程
// 并不是动态监听地址的变化，不过一般也不需要，我只写了一个发现的逻辑
// 首先进行第一次的监听，如果没有发现就需要开启协程进行一个异步监听
// 如果监听到就给discover发送关闭消息，select接受到之后就会退出结束这个
// 服务发现的异步协程，释放资源
// 如果没有监听到，会进入超时，也会把这个协程释放

var (
	discoverDone = make(chan struct{}) // 服务发现完成通知通道
)

func Close() {
	close(discoverDone)
}

// DiscoverEtcdService 阻塞式服务发现
func DiscoverEtcdService(etcd *config.Etcd) {
	cli, err := clientv3.New(clientv3.Config{
		Endpoints:   []string{"localhost:2379"},
		DialTimeout: 5 * time.Second,
	})
	if err != nil {
		log.Fatal("ETCD连接失败:", err)
	}
	defer cli.Close()

	// 对应注册的服务名
	key := "/services/" + etcd.Name

	// 来个上下文
	ctx, cancel := context.WithCancel(context.Background())
	// 首次查询
	resp, err := cli.Get(ctx, key)
	if !handleInitialDiscovery(resp, etcd, cancel) {
		log.Printf("服务 %s 未注册，启动监听...", etcd.Name)
	}

	// 启动异步监听
	go startServiceWatch(cli, key, etcd)

	// 阻塞等待直到发现服务地址
	select {
	case <-ctx.Done(): // 正常发现
		//这个阻塞的是监听，而下面那个是则是阻塞的整个服务
		log.Printf("成功发现服务地址: %s", etcd.Addr)
		select {
		case <-discoverDone:
			fmt.Println("关闭服务")
		}
	}
}

// 处理初次服务发现
func handleInitialDiscovery(resp *clientv3.GetResponse, etcd *config.Etcd, cancel context.CancelFunc) bool {
	if len(resp.Kvs) > 0 {
		// 取第一个健康实例（实际生产环境应做健康检查）
		etcd.Addr = string(resp.Kvs[0].Value)
		cancel()
		return true
	}
	return false
}

// 启动服务监听
func startServiceWatch(cli *clientv3.Client, key string, etcd *config.Etcd) {
	// 1. 创建 Watcher，监听指定 key
	watchChan := cli.Watch(context.TODO(), key)
	// 2. 持续接收事件流
	for watchResp := range watchChan {
		// 3. 遍历每个事件
		for _, event := range watchResp.Events {
			// 4. 处理事件
			processWatchEvent(event, etcd)
		}
	}
}

// 处理watch事件（返回是否完成发现）
func processWatchEvent(event *clientv3.Event, etcd *config.Etcd) {
	switch event.Type {
	case clientv3.EventTypePut:
		// 只取第一个发现的地址（根据需求可改为列表）
		newAddr := string(event.Kv.Value)
		if newAddr == "127.0.0.1:50051" {
			log.Printf("监听到服务地址: %s", newAddr)
		} else {
			log.Printf("监听服务地址发生改变: %s", newAddr)
		}
		etcd.Addr = newAddr
		//close(discoverDone) // 通知主流程继续执行

	case clientv3.EventTypeDelete:
		// 生产环境需要处理节点下线逻辑
		log.Printf("服务实例下线: %s", string(event.Kv.Key))
	}
}
